/*****************************************************************************
 *   Copyright 2005 Tim A Wang
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 ****************************************************************************/
package com.jb.web.lifecycle;

import com.jb.util.Meter;
import com.jb.web.context.WebContext;

import java.io.IOException;

import java.util.Iterator;
import java.util.Vector;

import com.jb.faces.application.FacesMessage;
import com.jb.faces.component.UIViewRoot;
import com.jb.faces.context.FacesContext;
import com.jb.faces.event.PhaseEvent;
import com.jb.faces.event.PhaseId;
import com.jb.faces.event.PhaseListener;
import com.jb.faces.lifecycle.Lifecycle;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


/**
 * Implemenation of Faces LifeCycle
 */
public class LifeCycleImpl extends Lifecycle // implements NWConstants //,Phase
 {
    //implemenation of Lifecycle
    Vector mListeners = new Vector();
    WebContext webContext = null;
    private Log log = LogFactory.getLog(getClass().getName());

    // Session session;

    /**
     * This is where a lifecycle starts. Web application will creat a lifeCycle at the time
     * when a request comes in. LifeCycle object ends when the response returned to the requestor
     *
     */
    public LifeCycleImpl() {
    }

    public LifeCycleImpl(WebContext pCtx) throws LifeCycleException {
        super();
        init(pCtx);
    }

    public PhaseListener[] getPhaseListeners() {
        return (PhaseListener[]) mListeners.toArray();
    }

    public WebContext getRequestContext() {
        return webContext;
    }

    public void addPhaseListener(PhaseListener pListener) {
        mListeners.add(pListener);
    }

    /**
     * Start a lifecycle for UI request.
     */
    public void execute(FacesContext pCtx) {
        if (log.isDebugEnabled()) {
            log.debug("Start execute lifecycle");
        }
        //allow registed decoder to decode the values that comes from the request
        if (!pCtx.getRenderResponse()) {
            try {
                processDecodes(pCtx);
            } catch (Exception e) {
                if (log.isFatalEnabled()) {
                    log.fatal(this, e);
                }
                pCtx.addMessage("LifeCycleImpl.execute",
                    new FacesMessage(FacesMessage.SEVERITY_FATAL, "Life cycle error : process decoding", e.getMessage()));
                pCtx.renderResponse();
            }
        }

        //validate decoded values, if goes ok, 
        if (!pCtx.getRenderResponse()) {
            try {
                processValidators(pCtx);
            } catch (Exception e) {
                if (log.isFatalEnabled()) {
                    log.fatal(this, e);
                }

                pCtx.addMessage("LifeCycleImpl.execute",
                    new FacesMessage(FacesMessage.SEVERITY_FATAL, "Life cycle error : process validation",
                        e.getMessage()));
                pCtx.renderResponse();
            }
        }
        
        if(((WebContext) pCtx).isError()) {//if there is any validation error, render results
            pCtx.renderResponse();
        }

        //fire update model event, registered update model handler will get fired here
        if (!pCtx.getRenderResponse()) {
            try {
                processUpdates(pCtx);
            } catch (Exception e) {
                if (log.isFatalEnabled()) {
                    log.fatal(this, e);
                }

                pCtx.addMessage("LifeCycleImpl.execute",
                    new FacesMessage(FacesMessage.SEVERITY_FATAL, "Life cycle error : processUpdate", e.getMessage()));
                pCtx.renderResponse();
            }
        }

        //does nothing for now
        if (!pCtx.getRenderResponse()) {
            try {
                invokeApplication(pCtx);
            } catch (Exception e) {
                if (log.isFatalEnabled()) {
                    log.fatal(this, e);
                }

                pCtx.addMessage("LifeCycleImpl.execute",
                    new FacesMessage(FacesMessage.SEVERITY_FATAL, "Life cycle error : invoke application",
                        e.getMessage()));
                pCtx.renderResponse();
            }
        }

        //return;
        if ((webContext.getRedirectPage() != null) && !pCtx.getRenderResponse()) {
            //if redirect is needed, we will do it
            try {
                webContext.getResponse().sendRedirect(webContext.getRedirectPage());
                webContext.isResponseComplete = true;
            } catch (IOException e) {
                if (log.isFatalEnabled()) {
                    log.fatal(this, e);
                }

                pCtx.addMessage("LifeCycle", new FacesMessage(FacesMessage.SEVERITY_FATAL, "Rediection", e.getMessage()));
                pCtx.renderResponse();
            }
        }

        if (log.isDebugEnabled()) {
            log.debug("Done execute lifecycle");
        }
    }

    public void init(WebContext pCtx) throws LifeCycleException {
        webContext = pCtx;
        this.restoreView(pCtx);
    }

    //
    // not defined yet
    //
    public void invokeApplication(FacesContext pCtx) {
        Meter.start();

        if (log.isDebugEnabled()) {
            log.debug("Start execute invokeApplication");
        }

        broadcastBeforeEvent(PhaseId.INVOKE_APPLICATION);
        setPhaseId(PhaseId.INVOKE_APPLICATION);
        pCtx.getViewRoot().processApplication(pCtx);
        broadcastAfterEvent(pCtx, PhaseId.INVOKE_APPLICATION);

        if (log.isDebugEnabled()) {
            log.debug("Done execute invokeApplication");
        }

        Meter.stop("invokeApplication");
    }

    //
    //decode the income message, and update values for component
    //
    public void processDecodes(FacesContext pCtx) {
        Meter.start();

        if (log.isDebugEnabled()) {
            log.debug("Start processDecodes");
        }

        setPhaseId(PhaseId.APPLY_REQUEST_VALUES);
        broadcastBeforeEvent(PhaseId.APPLY_REQUEST_VALUES);
        pCtx.getViewRoot().processDecodes(pCtx);
        broadcastAfterEvent(pCtx, PhaseId.APPLY_REQUEST_VALUES);

        if (log.isDebugEnabled()) {
            log.debug("Done processDecodes");
        }

        Meter.stop("processDecodes");
    }

    //
    //if validation and decode done and sucessful, we will call update model.
    //for the request tree, this will recursively call all updatemodel method
    //if a component has a method assigned for onupdatemodel, it will get called
    //if it is the source component.
    //
    public void processUpdates(FacesContext pCtx) {
        Meter.start();

        if (log.isDebugEnabled()) {
            log.debug("Start processUpdates");
        }

        // long l = System.currentTimeMillis();
        broadcastBeforeEvent(PhaseId.UPDATE_MODEL_VALUES);
        setPhaseId(PhaseId.UPDATE_MODEL_VALUES);
        pCtx.getViewRoot().processUpdates(pCtx);
        broadcastAfterEvent(pCtx, PhaseId.UPDATE_MODEL_VALUES);

        if (log.isDebugEnabled()) {
            log.debug("Done processUpdates");
        }

        Meter.stop("processUpdates");
    }

    //we have done decoding, values are updated for components
    //now validate the values if they have validators assiged
    public void processValidators(FacesContext pCtx) {
        //    long l = System.currentTimeMillis();
        Meter.start();

        if (log.isDebugEnabled()) {
            log.debug("Start processValidators");
        }

        broadcastBeforeEvent(PhaseId.PROCESS_VALIDATIONS);
        setPhaseId(PhaseId.PROCESS_VALIDATIONS);
        pCtx.getViewRoot().processValidators(pCtx);
        broadcastAfterEvent(pCtx, PhaseId.PROCESS_VALIDATIONS);

        if (log.isDebugEnabled()) {
            log.debug("Done processValidators");
        }

        Meter.stop("processValidators");
    }

    public void removePhaseListener(PhaseListener pListener) {
        mListeners.remove(pListener);
    }

    public void render(FacesContext pCtx) {
        Meter.start();

        setPhaseId(PhaseId.RENDER_RESPONSE);
        broadcastBeforeEvent(PhaseId.RENDER_RESPONSE);

        try {
            pCtx.getApplication().getViewHandler().renderView(webContext, webContext.getViewRoot());
        } catch (IOException e) {
            e.printStackTrace();
        }

        broadcastAfterEvent(pCtx, PhaseId.RENDER_RESPONSE);
        Meter.stop("render");
    }

    /**
     * First thing to do when a request comes in. It restores the view that
     * prepared for this request. All the input parameters are targeted at this
     * view. However, this view might not be the one will be rendered back
     * to user at the end of the life cycle.
     *
     * @param pCtx
     */
    private void restoreView(FacesContext pCtx) {
        Meter.start();

        if (log.isDebugEnabled()) {
            log.debug("Start restoreView");
        }

        //        long l = System.currentTimeMillis();
        setPhaseId(PhaseId.RESTORE_VIEW);
        broadcastBeforeEvent(PhaseId.RESTORE_VIEW);

        UIViewRoot view = pCtx.getViewRoot();

        if (view != null) {
            view.setLocale(pCtx.getExternalContext().getRequestLocale());
        }

        {
            String viewId = "view";
            view = pCtx.getApplication().getViewHandler().restoreView(pCtx, viewId);

            //
            //if nothing in paramters, we just jump into the renderresponse phase
            //per spec.
            if (webContext.getRequest().getParameterMap().isEmpty()) {
                pCtx.renderResponse();
            }

            pCtx.setViewRoot(view);
        }

        broadcastAfterEvent(pCtx, PhaseId.RESTORE_VIEW);

        if (log.isDebugEnabled()) {
            log.debug("Done restoreView");
        }

        Meter.stop("Restore View");
    }

    private void setPhaseId(PhaseId phaseId) {
        webContext.setPhase(phaseId);
    }

    private void broadcastAfterEvent(FacesContext pCtx, PhaseId phaseId) {
        PhaseEvent event = new PhaseEvent(pCtx, phaseId, this);

        for (Iterator i = mListeners.iterator(); i.hasNext();) {
            PhaseListener phaseListener = (PhaseListener) i.next();
            phaseListener.afterPhase(event);
        }
    }

    private void broadcastBeforeEvent(PhaseId phaseId) {
        PhaseEvent event = new PhaseEvent(webContext, phaseId, this);

        for (Iterator i = mListeners.iterator(); i.hasNext();) {
            PhaseListener phaseListener = (PhaseListener) i.next();
            phaseListener.beforePhase(event);
        }
    }
}

//
